PreparedStatementWriter.java
package org.codefilarete.stalactite.sql.statement.binder;
import java.lang.invoke.MethodHandleInfo;
import java.lang.invoke.SerializedLambda;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.function.Function;
import org.codefilarete.reflection.MethodReferenceCapturer.MethodDefinition;
import org.codefilarete.tool.function.SerializableThrowingFunction;
import org.codefilarete.tool.function.SerializableThrowingTriConsumer;
import org.codefilarete.tool.function.ThrowingTriConsumer;
import static org.codefilarete.reflection.MethodReferenceCapturer.giveArgumentTypes;
import static org.codefilarete.reflection.MethodReferences.buildSerializedLambda;
/**
* An interface that allows to use {@link PreparedStatement#setXXX(...)} methods to be used as method reference.
* See {@link DefaultPreparedStatementWriters} for some available ones.
* See {@link ResultSetReader} for its equivalence for reading {@link java.sql.ResultSet}, or
* {@link ParameterBinder} to accomplish both.
*
* @author Guillaume Mary
* @see ResultSetReader
* @see DefaultPreparedStatementWriters
*/
public interface PreparedStatementWriter<I> extends JdbcBinder<I> {
/**
* Creates a {@link PreparedStatementWriter} from a method reference to a {@link PreparedStatement} setter method.
* The type handled by the writer is inferred from the method reference.
*
* @param preparedStatementSetter a method reference to a {@link PreparedStatement} setXXX method
* @param <O> the type of the value to be set on the {@link PreparedStatement}
* @return a {@link PreparedStatementWriter} that invokes the specified {@link PreparedStatement} method
*/
static <O> PreparedStatementWriter<O> ofMethodReference(SerializableThrowingTriConsumer<PreparedStatement, Integer, O, SQLException> preparedStatementSetter) {
// because the targeted method is expected to be one of the setXXX(index, value), we collect the argument at index 1, not zero.
Class<O> argumentType = giveArgumentTypes(buildSerializedLambda(preparedStatementSetter)).getArgumentTypes()[1];
return new LambdaPreparedStatementWriter<>(preparedStatementSetter, argumentType);
}
/**
* Applies <code>value</code> at position <code>valueIndex</code> on <code>statement</code>.
*
* @param preparedStatement PreparedStatement to be used
* @param valueIndex parameter index to be set, value for first parameter of methods <code>Statement.setXXX(..)</code>
* @param value value to be passed as second argument of methods <code>Statement.setXXX(..)</code>
* @throws SQLException the exception thrown be the underlying access to the {@link PreparedStatement}
*/
void set(PreparedStatement preparedStatement, int valueIndex, I value) throws SQLException;
/**
* Builds a new {@link PreparedStatementWriter} from this one by applying a converter on the input object
*
* @param converter the {@link Function} that turns input value to the colum type
* @param <O> type of input accepted by resulting {@link PreparedStatementWriter}
* @return a new {@link PreparedStatementWriter} based on this one plus a converting {@link Function}
* @see ResultSetReader#thenApply(SerializableThrowingFunction)
*/
default <O> PreparedStatementWriter<O> preApply(SerializableThrowingFunction<O, I, ? extends Throwable> converter) {
SerializedLambda methodReference = buildSerializedLambda(converter);
MethodDefinition methodDefinition = giveArgumentTypes(methodReference);
Class<O> argumentType;
if (methodReference.getImplMethodKind() == MethodHandleInfo.REF_invokeStatic) {
// static method reference : input of function is the type of our new PreparedStatementWriter
argumentType = methodDefinition.getArgumentTypes()[0];
} else {
if (methodDefinition.getArgumentTypes().length == 0) {
// method reference is "regular one" : one of a class
argumentType = methodDefinition.getDeclaringClass();
} else {
// case of a captured argument : either an instance method reference, or a real lambda expression
argumentType = methodDefinition.getArgumentTypes()[0];
}
}
return new LambdaPreparedStatementWriter<O>((ps, valueIndex, value) -> {
try {
this.set(ps, valueIndex, converter.apply(value));
} catch (Throwable e) {
throw new RuntimeException(e);
}
}, argumentType) {
@Override
public <U> Class<U> getColumnType() {
return PreparedStatementWriter.this.getColumnType();
}
};
}
/**
* Class that helps to wrap a {@link PreparedStatement} method as a {@link PreparedStatementWriter}
* @param <I> type written by the writer
* @author Guillaume Mary
*/
class LambdaPreparedStatementWriter<I> implements PreparedStatementWriter<I> {
private final ThrowingTriConsumer<PreparedStatement, Integer, I, SQLException> delegate;
private final Class<I> type;
public LambdaPreparedStatementWriter(ThrowingTriConsumer<PreparedStatement, Integer, I, SQLException> delegate, Class<I> type) {
this.delegate = delegate;
this.type = type;
}
@Override
public Class<I> getType() {
return type;
}
@Override
public void set(PreparedStatement preparedStatement, int valueIndex, I value) throws SQLException {
delegate.accept(preparedStatement, valueIndex, value);
}
}
}